package com.hsgene.product.service.impl;

import com.alibaba.fastjson.JSONObject;
import com.hsgene.common.ClientType;
import com.hsgene.common.exception.CommonErrorCode;
import com.hsgene.common.util.basic.SimpleStringUtils;
import com.hsgene.common.util.exception.ErrorCode;
import com.hsgene.common.util.result.ApiResult;
import com.hsgene.common.util.support.LimitHelper;
import com.hsgene.common.util.support.Pagination;
import com.hsgene.product.dto.QuerySetMealDto;
import com.hsgene.product.dto.SetMealDto;
import com.hsgene.product.exception.ProductErrorCode;
import com.hsgene.product.persistence.CommodityRepository;
import com.hsgene.product.service.ElasticsearchService;
import org.apache.commons.lang.StringUtils;
import org.elasticsearch.action.admin.indices.analyze.AnalyzeAction;
import org.elasticsearch.action.admin.indices.analyze.AnalyzeRequestBuilder;
import org.elasticsearch.action.admin.indices.analyze.AnalyzeResponse;
import org.elasticsearch.action.delete.DeleteRequestBuilder;
import org.elasticsearch.action.delete.DeleteResponse;
import org.elasticsearch.action.get.GetRequestBuilder;
import org.elasticsearch.action.index.IndexRequestBuilder;
import org.elasticsearch.action.index.IndexResponse;
import org.elasticsearch.action.search.SearchRequestBuilder;
import org.elasticsearch.action.search.SearchResponse;
import org.elasticsearch.action.search.SearchType;
import org.elasticsearch.action.update.UpdateRequestBuilder;
import org.elasticsearch.action.update.UpdateResponse;
import org.elasticsearch.client.transport.TransportClient;
import org.elasticsearch.common.settings.Settings;
import org.elasticsearch.common.transport.TransportAddress;
import org.elasticsearch.common.xcontent.XContentType;
import org.elasticsearch.index.query.BoolQueryBuilder;
import org.elasticsearch.index.query.QueryBuilders;
import org.elasticsearch.index.reindex.BulkByScrollResponse;
import org.elasticsearch.index.reindex.BulkByScrollTask;
import org.elasticsearch.index.reindex.UpdateByQueryAction;
import org.elasticsearch.index.reindex.UpdateByQueryRequestBuilder;
import org.elasticsearch.rest.RestStatus;
import org.elasticsearch.script.Script;
import org.elasticsearch.script.ScriptType;
import org.elasticsearch.search.SearchHit;
import org.elasticsearch.search.SearchHits;
import org.elasticsearch.search.sort.SortOrder;
import org.elasticsearch.transport.client.PreBuiltTransportClient;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Bean;
import org.springframework.core.env.Environment;
import org.springframework.stereotype.Service;

import javax.annotation.Resource;
import java.net.InetAddress;
import java.net.UnknownHostException;
import java.util.*;
import java.util.stream.Collectors;

/**
 * @description:
 * @projectName: hdas-geneshop-server
 * @package: com.hsgene.product.service.impl
 * @author: maodi
 * @createDate: 2018/10/22 15:17
 * @version: 1.0
 * Copyright: Copyright (c) 2018
 */
@Service
public class ElasticsearchServiceImpl implements ElasticsearchService {


    @Resource
    private CommodityRepository commodityRepository;

    /**
     * 标准分词方法(会把字符串拆分成每个字符)
     */
    private final static String STANDARD = "standard";

    /**
     * ik_max_word分词方式(最细粒度的拆分)
     */
    private final static String IK_MAX_WORD = "ik_max_word";

    @Autowired
    private Environment env;

    private final static String INDEX = "spring.data.elasticsearch.properties.index";

    private final static String TYPE_NAME = "spring.data.elasticsearch.properties.type-name";

    private final static String QUERY_COLUMNS = "spring.data.elasticsearch.properties.query-columns";

    private final static String CLUSTER_NAME = "spring.data.elasticsearch.cluster-name";

    private final static String CLUSTER_NODES = "spring.data.elasticsearch.cluster-nodes";

    private final static String CLUSTER_NAME_KEY = "cluster.name";

    private static final String ES_ENABLE = "es.enable";

    /**
     * elasticsearch连接
     */
    private static TransportClient transportClient;

    /**
     * 索引
     */
    private static String index;

    /**
     * 类型
     */
    private static String typeName;

    private final static Logger LOGGER = LoggerFactory.getLogger(ElasticsearchServiceImpl.class);

    private static List<String> queryColumns;

    @Bean
    private TransportClient transportClient() {
        try {
            queryColumns = env.getProperty(QUERY_COLUMNS, ArrayList.class);
            index = env.getProperty(INDEX);
            typeName = env.getProperty(TYPE_NAME);
            Settings settings = Settings.builder().put(CLUSTER_NAME_KEY, env.getProperty(CLUSTER_NAME)).build();
            String[] clusterNodeArray = env.getProperty(CLUSTER_NODES).split(",");
            transportClient = new PreBuiltTransportClient(settings);
            for (String clusterNode : clusterNodeArray) {
                String[] hostAndPort = clusterNode.split(":");
                String host = hostAndPort[0];
                int port = Integer.valueOf(hostAndPort[1]);
                transportClient.addTransportAddresses(new TransportAddress(InetAddress.getByName(host), port));
            }
            return transportClient;
        } catch (UnknownHostException e) {
            LOGGER.error("获取es连接失败", e);
            return null;
        }
    }

    /**
     * @param setMealDto 套餐数据
     * @return org.elasticsearch.rest.RestStatus
     * @description 新增数据
     * @author maodi
     * @createDate 2018/10/31 17:32
     */
    @Override
    public RestStatus addSetMealDto(SetMealDto setMealDto) {
        IndexRequestBuilder builder = transportClient.prepareIndex(index, typeName, String.valueOf(setMealDto.getId()));
        setMealDto.setUpdateDateTime(new Date());
        String json = JSONObject.toJSONString(setMealDto);
        IndexResponse response = builder.setSource(json, XContentType.JSON).execute().actionGet();
        return response.status();
    }

    /**
     * @param id 套餐id
     * @return org.elasticsearch.rest.RestStatus
     * @description 根据id删除套餐数据
     * @author maodi
     * @createDate 2018/10/31 17:31
     */
    @Override
    public RestStatus deleteSetMealDto(String id) {
        DeleteRequestBuilder builder = transportClient.prepareDelete(index, typeName, String.valueOf(id));
        DeleteResponse response = builder.execute().actionGet();
        return response.status();
    }

    /**
     * @param setMealDto setMealDto 套餐数据
     * @return org.elasticsearch.rest.RestStatus
     * @description 修改套餐数据
     * @author maodi
     * @createDate 2018/10/26 11:50
     */
    @Override
    public RestStatus updateSetMealDto(SetMealDto setMealDto) {
        UpdateRequestBuilder builder = transportClient.prepareUpdate(index, typeName, String.valueOf(setMealDto.getId
                ()));
        setMealDto.setUpdateDateTime(new Date());
        String json = JSONObject.toJSONString(setMealDto);
        UpdateResponse response = builder.setDoc(json, XContentType.JSON).execute().actionGet();
        return response.status();
    }

    /**
     * @param id 套餐id
     * @return com.hsgene.product.dto.SetMealDto
     * @description
     * @author maodi
     * @createDate 2018/10/22 16:01
     */
    @Override
    public SetMealDto searchById(String id) {
        GetRequestBuilder builder = transportClient.prepareGet(index, typeName, String.valueOf(id));
        String json = builder.get().getSourceAsString();
        if (json == null) {
            return null;
        }
        return JSONObject.parseObject(json, SetMealDto.class);
    }

    /**
     * @param setMeal 套餐查询
     * @return com.hsgene.common.util.result.ApiResult<com.hsgene.common.util.support.Pagination<com.hsgene.product.dto.SetMealDto>>
     * @description
     * @author maodi
     * @createDate 2018/10/26 10:13
     */
    @Override
    public ApiResult<Pagination<SetMealDto>> search(QuerySetMealDto setMeal) {
        Integer page = setMeal.getPage();
        if (page != null && !SimpleStringUtils.isPositiveInteger(page.toString())) {
            return ApiResult.fail(CommonErrorCode.PAGE_IS_NOT_POSITIVE_INTEGER);
        }
        Integer size = setMeal.getSize();
        if (size != null && !SimpleStringUtils.isPositiveInteger(size.toString())) {
            return ApiResult.fail(CommonErrorCode.SIZE_IS_NOT_POSITIVE_INTEGER);
        }
        BoolQueryBuilder boolQueryBuilder = QueryBuilders.boolQuery();
        //添加查询条件
        ErrorCode code = addQueryBuilder(boolQueryBuilder, setMeal);
        if (code != null) {
            return ApiResult.fail(code);
        }
        SearchRequestBuilder searchBuilder = transportClient.prepareSearch(index).setTypes(typeName).setSearchType
                (SearchType.DFS_QUERY_THEN_FETCH);
        if (page != null && size != null) {
            int offset = (page - 1) * size;
            searchBuilder.setQuery(boolQueryBuilder).setFrom(offset).setSize(size);
        } else {
            searchBuilder.setQuery(boolQueryBuilder).setSize(1000);
        }
        //为字段添加排序
        code = addSortOrder(searchBuilder, setMeal);
        if (code != null) {
            return ApiResult.fail(code);
        }
        SearchResponse response = searchBuilder.execute().actionGet();
        RestStatus status = response.status();
        if (status.getStatus() != RestStatus.OK.getStatus()) {
            return ApiResult.fail(ProductErrorCode.SEARCH_SET_MEAL_FROM_ELASTICSEARCH_IS_ERROR, status.name());
        }
        SearchHits searchHits = response.getHits();
        List<SetMealDto> list = getSetMealDtos(searchHits);
        //如果有购物车状态，查询数据库以前购物车信息
        String userId = setMeal.getUserId();
        if (StringUtils.isNotBlank(userId)) {
            for (SetMealDto smd : list) {
                String packageId = smd.getId();
                int count = commodityRepository.getProductCountByPackageIdAndUserId(packageId, userId);
                if (count > 0) {
                    //1表示在购物车
                    smd.setCartStatus(1);
                } else {
                    //2表示没在购物车
                    smd.setCartStatus(2);
                }
            }
        }
        int totalCount = (int) searchHits.getTotalHits();
        LimitHelper limitHelper = LimitHelper.create(page == null ? 1 : page, size == null ? 1000 : size);
        Pagination<SetMealDto> pagination = Pagination.newInstance(limitHelper, totalCount);
        pagination.setResult(list);
        return ApiResult.succ(pagination);
    }

    private List<SetMealDto> getSetMealDtos(SearchHits searchHits) {
        SearchHit[] hits = searchHits.getHits();
        List<SetMealDto> list = new ArrayList<>(hits.length);
        for (SearchHit hit : hits) {
            String json = hit.getSourceAsString();
            list.add(JSONObject.parseObject(json, SetMealDto.class));
        }
        return list;
    }


    /**
     * @param boolQueryBuilder 查询
     * @param setMeal          套餐查询
     * @return com.hsgene.common.util.exception.ErrorCode
     * @description 为查询加入查询条件
     * @author maodi
     * @createDate 2018/10/26 10:53
     */
    private ErrorCode addQueryBuilder(BoolQueryBuilder boolQueryBuilder, QuerySetMealDto setMeal) {
        //后台查询条件
        if (setMeal.getPlatform().equals(ClientType.CONSOLE.getCode())) {
            //为套餐名字添加查询条件
            String name = setMeal.getName();
            if (StringUtils.isNotBlank(name)) {
                boolQueryBuilder.must(QueryBuilders.wildcardQuery("name.keyword", "*" + name.toLowerCase() + "*"));
            }
            //为机构名称添加条件
            String orgs = setMeal.getOrgs();
            if (StringUtils.isNotEmpty(orgs)) {
                boolQueryBuilder.must(QueryBuilders.wildcardQuery("orgs.name.keyword", "*" + orgs.toLowerCase() + "*"));
            }
            //排除某一个套餐
            if (StringUtils.isNotEmpty(setMeal.getExcludeId())) {
                List<String> excludeIds = Arrays.stream(setMeal.getExcludeId().split(",")).collect(Collectors.toList());
                boolQueryBuilder.mustNot(QueryBuilders.termsQuery("id", excludeIds));
                if (excludeIds.size() > 0) {
                    excludeIds.forEach(c -> {
                        boolQueryBuilder.mustNot(QueryBuilders.termsQuery("id", c));
                    });
                }
            }
            //为积分政策添加条件
            Integer policyType = setMeal.getPolicyType();
            if (policyType != null) {
                if (!SimpleStringUtils.isPositiveInteger(policyType.toString()) && policyType != 0) {
                    return ProductErrorCode.INTEGRATION_IS_NOT_POSITIVE_INTEGER;
                } else {
                    boolQueryBuilder.must(QueryBuilders.termQuery("policyType", policyType));
                }
            }
        } else {
            String condition = setMeal.getCondition();
            //为所有查询字段添加查询条件
            if (StringUtils.isNotBlank(condition)) {
                Set<String> termSet = mergeIKAndStandardTerms(condition);
                for (String column : queryColumns) {
                    for (String term : termSet) {
                        if ("integration".equals(column)) {
                            if (!SimpleStringUtils.isPositiveInteger(term) && !"0".equals(term)) {
                                continue;
                            }
                        }
                        boolQueryBuilder.should(QueryBuilders.matchPhraseQuery(column, term));
                    }
                }
            }
            //为套餐名字添加查询条件
            String name = setMeal.getName();
            if (StringUtils.isNotBlank(name)) {
                boolQueryBuilder.should(QueryBuilders.wildcardQuery("name", "*" + name + "*"));
            }
            //疾病添加查询条件
            if (StringUtils.isNotEmpty(setMeal.getCancerIds())) {
                String[] cancerIds = StringUtils.split(setMeal.getCancerIds(), ",");
                Arrays.stream(cancerIds).forEach(c -> {
                    boolQueryBuilder.should(QueryBuilders.matchPhraseQuery("cancers.id", c));
                });
            }
            if (StringUtils.isNotEmpty(setMeal.getCancerNames())) {
                String[] split = StringUtils.split(setMeal.getCancerNames(), ",");
                Arrays.stream(split).forEach(c -> {
                    boolQueryBuilder.should(QueryBuilders.matchPhraseQuery("cancers.name", c));
                });
            }
            //为药物名称添加查询条件
            if (StringUtils.isNotEmpty(setMeal.getMedicineIds())) {
                String[] strings = StringUtils.split(setMeal.getMedicineIds(), ",");
                Arrays.stream(strings).forEach(c -> {
                    boolQueryBuilder.should(QueryBuilders.matchPhraseQuery("medicines.medicineId", c));
                });
            }
            if (StringUtils.isNotEmpty(setMeal.getMedicineNames())) {
                String[] strings = StringUtils.split(setMeal.getMedicineNames(), ",");
                Arrays.stream(strings).forEach(c -> {
                    boolQueryBuilder.should(QueryBuilders.matchPhraseQuery("medicines.medicineName", c));
                });
            }
            //添加价格范围
            Float minPrice = setMeal.getMinPrice();
            Float maxPrice = setMeal.getMaxPrice();
            if (minPrice != null && maxPrice == null) {
                if (minPrice < 0) {
                    return ProductErrorCode.MIN_PRICE_IS_NEGATIVE;
                }
                boolQueryBuilder.must(QueryBuilders.rangeQuery("price").gte(minPrice));
            }
            if (minPrice == null && maxPrice != null) {
                //最高价格不能为负数
                if (maxPrice < 0) {
                    return ProductErrorCode.MAX_PRICE_IS_NEGATIVE;
                }
                boolQueryBuilder.must(QueryBuilders.rangeQuery("price").lte(maxPrice));
            }
            if (minPrice != null && maxPrice != null) {
                //最低价格不能为负数
                if (minPrice < 0) {
                    return ProductErrorCode.MIN_PRICE_IS_NEGATIVE;
                }
                //最高价格不能为负数
                if (maxPrice < 0) {
                    return ProductErrorCode.MAX_PRICE_IS_NEGATIVE;
                }
                //最低价格不能大于最高价格
                if (minPrice > maxPrice) {
                    return ProductErrorCode.MIN_PRICE_GREATER_THAN_MAX_PRICE;
                }
                boolQueryBuilder.must(QueryBuilders.rangeQuery("price").gte(minPrice).lte(maxPrice));
            }
            //为检测周期添加查询条件
            String testPeriod = setMeal.getTestPeriod();
            if (StringUtils.isNotBlank(testPeriod)) {
                boolQueryBuilder.should(QueryBuilders.matchPhraseQuery("testPeriod", testPeriod));
            }
            //为基因位点，靶标等添加查询条件
            String targets = setMeal.getTargets();
            if (StringUtils.isNotEmpty(targets)) {
                boolQueryBuilder.should(QueryBuilders.matchPhraseQuery("targets", targets));
            }
            //为积分添加查询条件
            Integer integration = setMeal.getIntegration();
            if (integration != null) {
                if (!SimpleStringUtils.isPositiveInteger(integration.toString())) {
                    return ProductErrorCode.INTEGRATION_IS_NOT_POSITIVE_INTEGER;
                } else {
                    boolQueryBuilder.should(QueryBuilders.matchPhraseQuery("integration", integration));
                }
            }
        }
        return null;
    }

    /**
     * @param searchRequestBuilder 查询
     * @param setMeal              套餐查询
     * @return com.hsgene.common.util.exception.ErrorCode
     * @description 为查询加排序
     * @author maodi
     * @createDate 2018/10/26 10:53
     */
    private ErrorCode addSortOrder(SearchRequestBuilder searchRequestBuilder, QuerySetMealDto setMeal) {
        String asc = SortOrder.ASC.name();
        String desc = SortOrder.DESC.name();
        if (!setMeal.getPlatform().equals(ClientType.CONSOLE.getCode())) {
            //价格排序
            if (StringUtils.isNotBlank(setMeal.getPriceOrder())) {
                String priceOrder = setMeal.getPriceOrder().toUpperCase();
                if (!asc.equals(priceOrder) && !desc.equals(priceOrder)) {
                    return ProductErrorCode.PRICE_SORT_ORDER_IS_ERROR;
                } else {
                    if (asc.equals(priceOrder)) {
                        searchRequestBuilder.addSort("price", SortOrder.ASC);
                    } else {
                        searchRequestBuilder.addSort("price", SortOrder.DESC);
                    }
                }
            }
            //佣金排序
            if (StringUtils.isNotBlank(setMeal.getCommissionOrder())) {
                String commissionOrder = setMeal.getCommissionOrder().toUpperCase();
                if (!asc.equals(commissionOrder) && !desc.equals(commissionOrder)) {
                    return ProductErrorCode.COMMISSION_SORT_ORDER_IS_ERROR;
                } else {
                    if (asc.equals(commissionOrder)) {
                        searchRequestBuilder.addSort("commission", SortOrder.ASC);
                    } else {
                        searchRequestBuilder.addSort("commission", SortOrder.DESC);
                    }
                }
            }
            /*if (StringUtils.isBlank(setMeal.getCommissionOrder()) && StringUtils.isBlank(setMeal.getPriceOrder())) {
                if (setMeal.getPlatform().equals(ClientType.PEANUT.getCode())) {
                    searchRequestBuilder.addSort("price", SortOrder.DESC);
                }
            }*/
            //数量排序(暂时不用)
            /*String numberOrder = setMeal.getNumberOrder().toUpperCase();
            if (!asc.equals(numberOrder) && !desc.equals(numberOrder)) {
                return ProductErrorCode.NUMBER_SORT_ORDER_IS_ERROR;
            } else {
                if (asc.equals(numberOrder)) {
                    searchRequestBuilder.addSort("number", SortOrder.ASC);
                } else {
                    searchRequestBuilder.addSort("number", SortOrder.DESC);
                }
            }*/
            //使用数量排序
            /*String usageCountOrder = setMeal.getUsageCountOrder().toUpperCase();
            if (!asc.equals(usageCountOrder) && !desc.equals(usageCountOrder)) {
                return ProductErrorCode.USAGE_COUNT_SORT_ORDER_IS_ERROR;
            } else {
                if (asc.equals(usageCountOrder)) {
                    searchRequestBuilder.addSort("usageCount", SortOrder.ASC);
                } else {
                    searchRequestBuilder.addSort("usageCount", SortOrder.DESC);
                }
            }*/
        } else {
            //更新时间排序
            String updateDateTimeOrder = setMeal.getUpdateDateTimeOrder().toUpperCase();
            if (!asc.equals(updateDateTimeOrder) && !desc.equals(updateDateTimeOrder)) {
                return ProductErrorCode.UPDATE_DATE_TIME_SORT_ORDER_IS_ERROR;
            } else {
                if (asc.equals(updateDateTimeOrder)) {
                    searchRequestBuilder.addSort("updateDateTime", SortOrder.ASC);
                } else {
                    searchRequestBuilder.addSort("updateDateTime", SortOrder.DESC);
                }
            }
        }


        return null;
    }

    /**
     * @param condition 用户的输入查询条件
     * @return java.util.List<org.elasticsearch.action.admin.indices.analyze.AnalyzeResponse.AnalyzeToken>
     * @description 利用IK拆分搜索条件
     * @author maodi
     * @createDate 2018/10/25 9:29
     */
    private List<AnalyzeResponse.AnalyzeToken> splitConditionByIK(String condition) {
        AnalyzeRequestBuilder requestBuilder = new AnalyzeRequestBuilder(transportClient, AnalyzeAction.INSTANCE,
                index, condition).setTokenizer(IK_MAX_WORD);
        return requestBuilder.execute().actionGet().getTokens();
    }

    /**
     * @param condition 利用Standard拆分搜索条件
     * @return java.util.List<org.elasticsearch.action.admin.indices.analyze.AnalyzeResponse.AnalyzeToken>
     * @description 用户的输入查询条件
     * @author maodi
     * @createDate 2018/10/26 15:29
     */
    private List<AnalyzeResponse.AnalyzeToken> splitConditionByStandard(String condition) {
        AnalyzeRequestBuilder requestBuilder = new AnalyzeRequestBuilder(transportClient, AnalyzeAction.INSTANCE,
                index, condition).setTokenizer(STANDARD);
        return requestBuilder.execute().actionGet().getTokens();
    }

    /**
     * @param condition 查询条件
     * @return java.util.Set<java.lang.String>
     * @description 合并ik分词和standard分词的结果为set
     * @author maodi
     * @createDate 2018/10/26 15:38
     */
    private Set<String> mergeIKAndStandardTerms(String condition) {
        List<AnalyzeResponse.AnalyzeToken> tokensByIkAndStandard = new ArrayList<>();
        List<AnalyzeResponse.AnalyzeToken> ikTokens = splitConditionByIK(condition);
        if (ikTokens != null && ikTokens.size() > 0) {
            tokensByIkAndStandard.addAll(ikTokens);
        }
        List<AnalyzeResponse.AnalyzeToken> standardTokens = splitConditionByStandard(condition);
        if (standardTokens != null && standardTokens.size() > 0) {
            tokensByIkAndStandard.addAll(standardTokens);
        }
        Set<String> termSet = new HashSet<>();
        for (AnalyzeResponse.AnalyzeToken analyzeToken : tokensByIkAndStandard) {
            String term = analyzeToken.getTerm();
            if (!termSet.contains(term)) {
                termSet.add(term);
            }
        }
        return termSet;
    }

    /**
     * @param queryName  查询的名字("org.name")
     * @param queryValue 查询的值("云芝糖肽1")
     * @param idOrCode   for(HashMap m : ctx._source.org) {if(m.get('name') == '云芝糖肽1'){m.put('name','云芝糖肽')}}
     * @return org.elasticsearch.index.reindex.BulkByScrollTask.Status
     * @description 查询更新elasticsearch
     * @author maodi
     * @createDate 2019/1/3 15:59
     */
    @Override
    public BulkByScrollTask.Status updateByQuery(String queryName, String queryValue, String idOrCode) {
        UpdateByQueryRequestBuilder updateByQuery = UpdateByQueryAction.INSTANCE.newRequestBuilder(transportClient);
        updateByQuery.source(index).script(new Script(ScriptType.INLINE, "painless", idOrCode,
                Collections.emptyMap())).filter(QueryBuilders.termQuery(queryName, queryValue))
                .abortOnVersionConflict(false);
        BulkByScrollResponse response = updateByQuery.get();
        return response.getStatus();
    }

}
